﻿namespace Items
    {
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Windows.Media;
    using PowerSong.SongDatabase;
    using PowerSong.SongDatabase.ObjectModel;
    using PowerSong.UserInterface;
    using Legacy = PowerSong.SongDatabase.Legacy;

    /// <summary>
    /// Represents a PowerSong play list, which in turn comprises zero or more items that can be projected.
    /// </summary>
    public class PlayList
        {

        /// <summary>
        /// Identifies modern-style play lists created in versions of PowerSong less than version 1.0.0.
        /// </summary>
        private const int Version2 = 20100131;

        /// <summary>
        /// Identifies modern-style play lists created in this version of PowerSong.
        /// </summary>
        private const int Version3 = 20120430;

        private const string SongMissingMessage = "There is a song referenced in the play list that does not appear to exist in the database, therefore the song has not been added.";

        private const string KnownSongMissingMessage = "There is a song called '{0}' referenced in the play list that does not appear to exist in the database, therefore the song has not been added.";

        private List<ServiceItem> items = new List<ServiceItem>();

        /// <summary>
        /// Gets or sets the items in the play list, accessing by index.
        /// </summary>
        /// <value>The items in the play list.</value>
        public ServiceItem this[int index]
            {
            get { return items[index]; }
            set { items[index] = value; }
            }

        /// <summary>
        /// Adds the specified item to the play list.
        /// </summary>
        /// <param name="item">The item to add to the play list.</param>
        public void Add(ServiceItem item)
            {
            items.Add(item);
            }

        /// <summary>
        /// Returns the number of items contained in this play list.
        /// </summary>
        /// <returns>The number of items in the play list.</returns>
        public int Count()
            {
            return items.Count;
            }

        /// <summary>
        /// Removes the specified item from the play list.
        /// </summary>
        /// <param name="item">The item to remove from the play list. It must exist in the list.</param>
        public void Remove(ServiceItem item)
            {
            items.Remove(item);
            }

        /// <summary>
        /// Loads the given play list.
        /// Files ending in the extension ".pspl" are regarded as old style play lists, where only the IDs of songs are stored.
        /// Other files are loaded as modern style play lists, which contain far more information about each item in the list.
        /// </summary>
        /// <param name="database">The database the play list refers to.</param>
        /// <param name="fileName">The file name of the playlist to load.</param>
        public static PlayList Load(Database database, string fileName, List<string> messages)
            {

            if (Path.GetExtension(fileName).ToUpper() == ".PSPL")
                {
                return LoadClassic(database, fileName, messages);
                }
            else
                {
                return LoadModern(database, fileName, messages);
                }

            }

        /// <summary>
        /// Loads the given classic playlist file.
        /// </summary>
        /// <param name="database">The database the play list refers to.</param>
        /// <param name="fileName">The playlist filename.</param>
        private static PlayList LoadClassic(Database database, string fileName, List<string> messages)
            {

            // Load the play list            
            var result = new PlayList();
            var defaultStyle = State.Settings.DefaultStyle;
            using (var file = new BinaryReader(new FileStream(fileName, FileMode.Open)))
                {
                while (file.BaseStream.Position < file.BaseStream.Length)
                    {

                    // Add the song to the live playlist
                    var nextSongID = new Guid(file.ReadString());
                    var song = database.Songs.Get(nextSongID);
                    if (song != null)
                        {
                        result.Add(new SongServiceItem(song, defaultStyle));
                        }
                    else
                        {
                        messages.Add(SongMissingMessage);
                        }

                    }
                }

            // Return the play list
            return result;

            }

        /// <summary>
        /// Loads the given modern playlist file.
        /// </summary>
        /// <param name="database">The database the play list refers to.</param>
        /// <param name="fileName">The playlist filename.</param>
        private static PlayList LoadModern(Database database, string fileName, List<string> messages)
            {

            // Load the songs in the playlist            
            var result = new PlayList();
            using (var file = new BinaryReader(new FileStream(fileName, FileMode.Open)))
                {

                // Check version
                var version = file.ReadInt32();
                switch (version)
                    {

                    case Version2: return LoadVersion2(database, file, messages);
                    case Version3: return LoadVersion3(database, file, messages);

                    default:
                        throw new Exception(version > Version2
                                            ? "The specified playlist was created in a newer version of PowerSong and therefore cannot be loaded."
                                            : "The specified playlist was created in an early version of PowerSong and therefore cannot be loaded.");

                    }

                }

            }

#warning Test style information being saved and retrieved via PowerSong 1 and 2 Play List files

        private static PlayList LoadVersion2(Database database, BinaryReader file, List<string> messages)
            {

            // Keep reading until we reach the end of the file
            var result = new PlayList();
            while (file.BaseStream.Position < file.BaseStream.Length)
                {

                // Determine the type of the next item being read in
                ServiceItem nextItem = null;
                var itemType = file.ReadString();
                if (itemType == "SongItem")
                    {

                    // Extract details about the song
                    var songID = new Guid(file.ReadString());
                    var title = file.ReadString();
                    var styleID = new Guid(file.ReadString());
                    var copyrightLine = file.ReadString();

                    // Add the song to the playlist
                    if (database.Songs.Contains(songID))
                        {
                        var song = database.Songs.Get(songID);
                        nextItem = new SongServiceItem(song, database.Styles.Get(styleID));
                        }
                    else messages.Add(string.Format(KnownSongMissingMessage, title));

                    }
                else if (itemType == "AdhocItem")
                    {

                    // Extract details about the adhoc item
                    var adhocItem = new Song();
                    adhocItem.Title = file.ReadString();
                    adhocItem.Verses.AddRange(Utilities.TextToVerses(file.ReadString()));
                    var styleID = new Guid(file.ReadString());
                    adhocItem.Copyright = file.ReadString();
                    nextItem = new NoticeServiceItem(adhocItem, database.Styles.Get(styleID));

                    }
                else
                    {
                    throw new NotImplementedException("An unknown type of item is contained in the play list.");
                    }

                // Has the background been overridden?
                if (file.ReadBoolean() == true)
                    {

                    // Get the type of override
                    var type = (Legacy.Style.EBackgroundType)Enum.Parse(typeof(Legacy.Style.EBackgroundType), file.ReadString());
                    var value = file.ReadString();

                    // Apply the override, but only if we are loading an adhoc item, or the non-adhoc song actually exists
                    if (nextItem != null)
                        {
                        var itemStyle = (IStyledServiceItem)nextItem;
                        if (type == Legacy.Style.EBackgroundType.Image)
                            {
                            itemStyle.StyleOverride = StyleOverride.CreateImage(value);
                            }
                        else if (type == Legacy.Style.EBackgroundType.SolidColour)
                            {
                            var colourBytes = BitConverter.GetBytes(Convert.ToInt32(value));
                            var colour = Color.FromArgb(colourBytes[3], colourBytes[2], colourBytes[1], colourBytes[0]);
                            itemStyle.StyleOverride = StyleOverride.CreateColour(colour);
                            }
                        else
                            {
                            throw new NotImplementedException("An unknown type of overriding background is contained in the play list.");
                            }
                        }

                    }

                // Add the item to the list
                if (nextItem != null) result.Add(nextItem);

                }

            // Return the play list
            return result;

            }

        private static PlayList LoadVersion3(Database database, BinaryReader file, List<string> messages)
            {

            // Load all the items in the playlist
            var playList = new PlayList();
            while (file.BaseStream.Position < file.BaseStream.Length)
                {
                var type = file.ReadString();
                ServiceItem nextItem = null;
                if (type == "Song")
                    {

                    // This is a song
                    var identifier = new Guid(file.ReadString());
                    var title = file.ReadString();
                    var verses = Utilities.TextToVerses(file.ReadString());
                    var copyright = file.ReadString();
                    var song = database.Songs.Get(identifier);
                    if (song == null) song = new Song(title, verses) { ID = identifier, Copyright = copyright };
                    nextItem = new SongServiceItem(song, song.DefaultStyle ?? State.Settings.DefaultStyle);
                    ReadStyleDetails(database.Styles, file, (IStyledServiceItem)nextItem);

                    }
                else if (type == "Notice")
                    {

                    // This is an adhoc entry
                    var identifier = new Guid(file.ReadString());
                    var title = file.ReadString();
                    var verses = Utilities.TextToVerses(file.ReadString());
                    var copyright = file.ReadString();
                    var notice = new Song(title, verses) { ID = identifier, Copyright = copyright };
                    nextItem = new NoticeServiceItem(notice, State.Settings.DefaultStyle);
                    ReadStyleDetails(database.Styles, file, (IStyledServiceItem)nextItem);

                    }
                else if (type == "Banner")
                    {

                    // This is a notice banner
                    var title = file.ReadString();
                    var text = file.ReadString();
                    nextItem = new BannerServiceItem(title, text);

                    }
                else if (type == "BibleChapter")
                    {

                    // This is a bible chapter
                    var identifier = new Guid(file.ReadString());
                    var title = file.ReadString();
                    var verses = Utilities.TextToVerses(file.ReadString());
                    var copyright = file.ReadString();
                    var chapter = new Song(title, verses) { ID = identifier, Copyright = copyright };
                    nextItem = new BibleServiceItem(chapter, State.Settings.DefaultStyle);
                    ReadStyleDetails(database.Styles, file, (IStyledServiceItem)nextItem);

                    }
                else if (type == "Video")
                    {

                    // This is a video
                    var title = file.ReadString();
                    var fileName = file.ReadString();
                    nextItem = new VideoServiceItem(title, fileName);

                    }
                else
                    {
                    throw new NotImplementedException("An unknown type of item is contained in the play list.");
                    }

                // Add the item to the play list
                if (nextItem != null) playList.Add(nextItem);

                }

            // Return the loaded play list
            return playList;

            }

        /// <summary>
        /// Saves the play list to the specified file name.
        /// Files ending in the extension ".pspl" are regarded as old style play lists, where only the IDs of songs are stored.
        /// Other files are saved as modern style play lists, which contain far more information about each item in the list.
        /// </summary>
        /// <param name="fileName">
        /// Name of the file to save to.
        /// If the extension of the file is "pspl", then the format of the play list will be the classic "simple" type.
        /// </param>
        public void Save(string fileName)
            {
            if (Path.GetExtension(fileName).ToUpper() == ".PSPL")
                {
                SaveClassic(fileName);
                }
            else
                {
                SaveModern(fileName);
                }
            }

        /// <summary>
        /// Saves the given playlist file the "old" way, which simply creates a file of guids.
        /// </summary>
        /// <param name="fileName">The playlist filename.</param>
        private void SaveClassic(string fileName)
            {
            using (var file = new BinaryWriter(new FileStream(fileName, FileMode.Create)))
                {

                // Simply write the Guid of each referenced song in the list
                foreach (var item in items)
                    {
                    if (item is SongServiceItem)
                        {
                        var song = ((SongServiceItem)item).Song;
                        file.Write(song.ID.ToString());
                        }
                    }

                }
            }

        /// <summary>
        /// Saves the given playlist file using the "new" method, which includes the style selected as
        /// well as playlist version management.
        /// </summary>
        /// <param name="fileName">The playlist filename.</param>
        private void SaveModern(string fileName)
            {

            // Write the current playlist version
            using (var file = new BinaryWriter(new FileStream(fileName, FileMode.Create)))
                {
                file.Write(Version3);

                // Save all the items in the playlist
                foreach (var prospectiveItem in items)
                    {

                    if (prospectiveItem is SongServiceItem)
                        {

                        // This is a song
                        file.Write("Song");
                        var item = (SongServiceItem)prospectiveItem;
                        var song = item.Song;
                        var verses = Utilities.VersesToText(song.Verses);
                        WriteStrings(file, song.ID.ToString(), song.Title, verses, song.Copyright);
                        WriteStyleDetails(file, item.Style, item.StyleOverride);

                        }
                    else if (prospectiveItem is NoticeServiceItem)
                        {

                        // This is an adhoc entry
                        file.Write("Notice");
                        var item = (NoticeServiceItem)prospectiveItem;
                        var adhoc = item.Notice;
                        var verses = Utilities.VersesToText(adhoc.Verses);
                        WriteStrings(file, adhoc.ID.ToString(), adhoc.Title, verses, adhoc.Copyright);
                        WriteStyleDetails(file, item.Style, item.StyleOverride);

                        }
                    else if (prospectiveItem is BannerServiceItem)
                        {

                        // This is a notice banner
                        file.Write("Banner");
                        var item = (BannerServiceItem)prospectiveItem;
                        WriteStrings(file, item.Title, item.Text);

                        }
                    else if (prospectiveItem is BibleServiceItem)
                        {

                        // This is a bible chapter
                        file.Write("BibleChapter");
                        var item = (BibleServiceItem)prospectiveItem;
                        var verses = Utilities.VersesToText(item.Chapter.Verses);
                        WriteStrings(file, item.Chapter.ID.ToString(), item.Chapter.Title, verses, item.Chapter.Copyright);
                        WriteStyleDetails(file, item.Style, item.StyleOverride);

                        }
                    else if (prospectiveItem is VideoServiceItem)
                        {

                        // This is a video
                        file.Write("Video");
                        var item = (VideoServiceItem)prospectiveItem;
                        WriteStrings(file, item.Title, item.FileName);

                        }
                    else
                        {
                        throw new NotImplementedException("Cannot write song list item details because the item type is unknown.");
                        }

                    }

                }

            }

        private void WriteStrings(BinaryWriter file, params string[] strings)
            {
            foreach (var data in strings)
                {
                file.Write(data);
                }
            }

        private static void ReadStyleDetails(Styles styles, BinaryReader file, IStyledServiceItem item)
            {

            // Get the style ID and use it to assign the appropriate style to the service item
            var styleAssigned = file.ReadBoolean();
            if (styleAssigned)
                {
                var styleID = new Guid(file.ReadString());
                item.Style = styles.Get(styleID);
                }

            // Get overwritten background if one has been assigned
            var overrideDefined = file.ReadBoolean();
            if (overrideDefined)
                {
                var colour = file.ReadInt32();
                var imageFileName = file.ReadString();
                var videoFileName = file.ReadString();
                if (!string.IsNullOrEmpty(imageFileName))
                    {
                    item.StyleOverride = StyleOverride.CreateImage(imageFileName);
                    }
                else if (!string.IsNullOrEmpty(videoFileName))
                    {
                    item.StyleOverride = StyleOverride.CreateVideo(videoFileName);
                    }
                else
                    {
                    var bytes = BitConverter.GetBytes(colour);
                    item.StyleOverride = StyleOverride.CreateColour(Color.FromArgb(bytes[3], bytes[2], bytes[1], bytes[0]));
                    }
                }

            }

        private static void WriteStyleDetails(BinaryWriter file, Style style, StyleOverride styleOverride)
            {

            // Write the style ID identifying the basic style used
            if (style != null)
                {
                file.Write(true);
                file.Write(style.ID.ToString());
                }
            else
                {
                file.Write(false);
                }

            // Write overwritten background if one is available
            if (styleOverride != null)
                {
                file.Write(true);
                var colour = styleOverride.Colour.Color;
                file.Write((int)colour.A + ((int)colour.R << 8) + ((int)colour.G << 16) + ((int)colour.B << 24));
                file.Write(styleOverride.ImageFileName);
                file.Write(styleOverride.VideoFileName);
                }
            else
                {

                // False indicates there is no overriding of the basic style
                file.Write(false);

                }

            }

        }

    }
